package asdf.trial.mybatis;

import org.apache.ibatis.executor.parameter.ParameterHandler;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.reflection.DefaultReflectorFactory;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.ReflectorFactory;
import org.apache.ibatis.reflection.factory.DefaultObjectFactory;
import org.apache.ibatis.reflection.factory.ObjectFactory;
import org.apache.ibatis.reflection.wrapper.DefaultObjectWrapperFactory;
import org.apache.ibatis.reflection.wrapper.ObjectWrapperFactory;
import org.apache.ibatis.scripting.defaults.DefaultParameterHandler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.Map;
import java.util.Properties;
import java.util.regex.Pattern;


@Intercepts( { @Signature( method = "prepare", type = StatementHandler.class, args = {
        Connection.class } ) } )
public class PaginationInterceptor implements Interceptor
{

    private static final Logger LGR = LoggerFactory.getLogger(PaginationInterceptor.class);

    /**
     * 拦截后要执行的方法
     */
    @Override
    public Object intercept(Invocation invocation) throws Throwable
    {
        StatementHandler statementHandler = (StatementHandler) invocation.getTarget();
        MetaObject metaStatementHandler = this.getMetaObject(statementHandler);

        // 分离代理对象链(由于目标类可能被多个拦截器拦截，从而形成多次代理，通过下面的两次循环
        // 可以分离出最原始的的目标类)
        while ( metaStatementHandler.hasGetter("h") ) {
            Object object = metaStatementHandler.getValue("h");
            metaStatementHandler = this.getMetaObject(object);
        }
        // 分离最后一个代理对象的目标类
        while ( metaStatementHandler.hasGetter("target") ) {
            Object object = metaStatementHandler.getValue("target");
            metaStatementHandler = this.getMetaObject(object);
        }

        // 判断参数里是否有Pagination对象
        MappedStatement mappedStatement =
                (MappedStatement) metaStatementHandler.getValue("delegate.mappedStatement");
        BoundSql boundSql = (BoundSql) metaStatementHandler.getValue("delegate.boundSql");
        Object obj = boundSql.getParameterObject();
        Pagination page = null;
        if ( obj != null ) {
            if ( obj instanceof Pagination ) {
                page = (Pagination) obj;
            } else if ( obj instanceof Map ) {
                Map<?, ?> params = (Map<?, ?>) obj;
                for ( Object value : params.values() ) {
                    if ( value instanceof Pagination ) {
                        page = (Pagination) value;
                        break;
                    }
                }
            }
        }

        // 通过Pagination参数作分页操作
        if ( page != null ) {
            String sql = boundSql.getSql();
            // 获取总数
            Connection connection = (Connection) invocation.getArgs()[0];
            setTotalItemCount(page, mappedStatement, connection, boundSql);
            // 给当前的page参数对象设置总记录数
            // this.setTotalRecord(page, mappedStatement, connection);
            // 获取分页Sql语句
            String pageSql = sql + " limit " + page.offset + ", " + page.limit;
            // 利用反射设置当前BoundSql对应的sql属性为我们建立好的分页Sql语句
            MetaObject moBoundSql = this.getMetaObject(boundSql);
            moBoundSql.setValue("sql", pageSql);
        }

        return invocation.proceed();
    }

    /**
     * 拦截器对应的封装原始对象的方法
     */
    @Override
    public Object plugin(Object target)
    {
        return Plugin.wrap(target, this);
    }

    /**
     * 设置注册拦截器时设定的属性
     */
    @Override
    public void setProperties(Properties properties)
    {

    }

    private static final Pattern ORDERBY_PTN =
            Pattern.compile("order\\s+by\\s+\\S+(\\s(asc|desc))?(,\\s+\\S+(\\s(asc|desc)))*",
                            Pattern.CASE_INSENSITIVE);

    /**
     * 给当前的参数对象page设置总记录数
     *
     * @param page            Mapper映射语句对应的参数对象
     * @param mappedStatement Mapper映射语句
     * @param connection      当前的数据库连接
     */
    private void setTotalItemCount(
            Pagination page, MappedStatement mappedStatement, Connection connection,
            BoundSql boundSql)
    {
        String sql = boundSql.getSql().replace(System.lineSeparator(), "");
        String countSql = "select count(*) from (" + ORDERBY_PTN.matcher(sql).replaceFirst("")
                          + ") inner_query_for_count";
        // 通过BoundSql获取对应的参数映射
        // 利用Configuration、查询记录数的Sql语句countSql、参数映射关系parameterMappings和参数对象page建立查询记录数对应的BoundSql对象。
        BoundSql countBoundSql = new BoundSql(mappedStatement.getConfiguration(), countSql,
                                              boundSql.getParameterMappings(),
                                              boundSql.getParameterObject());
        // 通过mappedStatement、参数对象page和BoundSql对象countBoundSql建立一个用于设定参数的ParameterHandler对象
        ParameterHandler parameterHandler =
                new DefaultParameterHandler(mappedStatement, boundSql.getParameterObject(),
                                            countBoundSql);

        PreparedStatement pstmt = null;
        ResultSet rs = null;
        try {
            // 通过connection建立一个countSql对应的PreparedStatement对象。
            pstmt = connection.prepareStatement(countSql);
            // 通过parameterHandler给PreparedStatement对象设置参数
            parameterHandler.setParameters(pstmt);
            // 之后就是执行获取总记录数的Sql语句和获取结果了。
            rs = pstmt.executeQuery();
            if ( rs.next() ) {
                int totalRecord = rs.getInt(1);
                // 给当前的参数page对象设置总记录数
                page.setTotalCount(totalRecord);
            }
        } catch ( Exception e ) {
            LGR.error("select total count for SqlID[" + mappedStatement.getId() + "] failed.", e);
        } finally {
            try {
                if ( rs != null ) {
                    rs.close();
                }
                if ( pstmt != null ) {
                    pstmt.close();
                }
            } catch ( Exception e ) {
                LGR.error("", e);
            }
        }
    }

    private static final ObjectFactory        DEFAULT_OBJECT_FACTORY         =
            new DefaultObjectFactory();
    private static final ObjectWrapperFactory DEFAULT_OBJECT_WRAPPER_FACTORY =
            new DefaultObjectWrapperFactory();
    private static final ReflectorFactory     DEFAULT_REFLECTOR_FACTORY      =
            new DefaultReflectorFactory();

    private MetaObject getMetaObject(Object obj)
    {
        return MetaObject.forObject(
                obj,
                DEFAULT_OBJECT_FACTORY,
                DEFAULT_OBJECT_WRAPPER_FACTORY,
                DEFAULT_REFLECTOR_FACTORY);
    }

}